﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using nHydrate.Generator;
using nHydrate.Generator.Common.Util;
using System.Windows.Forms;
using nHydrate.Dsl;
using nHydrate.Generator.ModelUI;

namespace nHydrate.DslPackage.Objects
{
	internal static class ModelGenerateDomain
	{
		public static void Generate(nHydrateModel model, Microsoft.VisualStudio.Modeling.Diagrams.Diagram diagram, Microsoft.VisualStudio.Modeling.Shell.ModelingDocData docData)
		{
			try
			{
				#region Generation

				//Clean up delete tracking
				model.RemovedTables.Remove(x => model.Entities.Select(y => y.PascalName).Contains(x));
				model.RemovedViews.Remove(x => model.Views.Select(y => y.PascalName).Contains(x));
				model.RemovedStoredProcedures.Remove(x => model.StoredProcedures.Select(y => y.PascalName).Contains(x));
				model.RemovedFunctions.Remove(x => model.Functions.Select(y => y.PascalName).Contains(x));

				var genList = new List<nHydrateGeneratorProject>();
				if (model.UseModules)
				{
					foreach (var module in model.Modules)
					{
						var genProject = new nHydrateGeneratorProject();
						genList.Add(genProject);
						var root = CreatePOCOModel(model, diagram, module);
						((nHydrate.Generator.Models.ModelRoot)root).SetKey(model.Id.ToString());
						root.GeneratorProject = genProject;
						genProject.RootController.Object = root;
						var fi = new System.IO.FileInfo(docData.FileName);
						genProject.FileName = docData.FileName + "." + module.Name + ".generating";
						var document = new System.Xml.XmlDocument();
						document.LoadXml("<modelRoot guid=\"" + module.Id + "\" type=\"nHydrate.Generator.nHydrateGeneratorProject\" assembly=\"nHydrate.Generator.dll\"><ModelRoot></ModelRoot></modelRoot>");
						((nHydrate.Generator.Common.GeneratorFramework.IXMLable)root).XmlAppend(document.DocumentElement.ChildNodes[0]);
						System.IO.File.WriteAllText(genProject.FileName, document.DocumentElement.OuterXml);

						((nHydrate.Generator.Models.ModelRoot)root).RemovedTables.AddRange(model.RemovedTables);
						((nHydrate.Generator.Models.ModelRoot)root).RemovedViews.AddRange(model.RemovedViews);
						((nHydrate.Generator.Models.ModelRoot)root).RemovedStoredProcedures.AddRange(model.RemovedStoredProcedures);
						((nHydrate.Generator.Models.ModelRoot)root).RemovedFunctions.AddRange(model.RemovedFunctions);
					}
				}
				else
				{
					var genProject = new nHydrateGeneratorProject();
					genList.Add(genProject);
					var root = CreatePOCOModel(model, diagram, null);
					((nHydrate.Generator.Models.ModelRoot)root).SetKey(model.Id.ToString());
					root.GeneratorProject = genProject;
					genProject.RootController.Object = root;
					var fi = new System.IO.FileInfo(docData.FileName);
					genProject.FileName = docData.FileName + ".generating";
					var document = new System.Xml.XmlDocument();
					document.LoadXml("<modelRoot guid=\"" + model.Id + "\" type=\"nHydrate.Generator.nHydrateGeneratorProject\" assembly=\"nHydrate.Generator.dll\"><ModelRoot></ModelRoot></modelRoot>");
					((nHydrate.Generator.Common.GeneratorFramework.IXMLable)root).XmlAppend(document.DocumentElement.ChildNodes[0]);
					System.IO.File.WriteAllText(genProject.FileName, document.DocumentElement.OuterXml);

					((nHydrate.Generator.Models.ModelRoot)root).RemovedTables.AddRange(model.RemovedTables);
					((nHydrate.Generator.Models.ModelRoot)root).RemovedViews.AddRange(model.RemovedViews);
					((nHydrate.Generator.Models.ModelRoot)root).RemovedStoredProcedures.AddRange(model.RemovedStoredProcedures);
					((nHydrate.Generator.Models.ModelRoot)root).RemovedFunctions.AddRange(model.RemovedFunctions);
				}

				//Perform actual generation
				var threader = new GenerateThreader(genList, model, diagram.Store, docData);
				threader.Run();

				model.RemovedTables.Clear();
				model.RemovedViews.Clear();
				model.RemovedStoredProcedures.Clear();
				model.RemovedFunctions.Clear();

				#endregion

				//Remove temp file
				try
				{
					genList.ForEach(x => System.IO.File.Delete(x.FileName));
				}
				catch { }

#if DEBUG
				if (threader.ErrorList.Count > 0)
				{
					var F = new nHydrate.DslPackage.Forms.ErrorForm();
					F.SetErrors(threader.ErrorList);
					F.ShowDialog();
				}
#endif

			}
			catch (Exception ex)
			{
				throw;
			}
		}

		private static nHydrate.Generator.Models.ModelRoot CreatePOCOModel(nHydrateModel model, Microsoft.VisualStudio.Modeling.Diagrams.Diagram diagram, Module ownerModule)
		{
			try
			{
				var root = new nHydrate.Generator.Models.ModelRoot(null);
				root.TransformNames = model.TransformNames;
				root.CompanyName = model.CompanyName;
				root.Copyright = model.Copyright;
				root.DefaultNamespace = model.DefaultNamespace;
				root.ProjectName = model.ProjectName;
				root.SQLServerType = (nHydrate.Generator.Models.SQLServerTypeConstants)Enum.Parse(typeof(nHydrate.Generator.Models.SQLServerTypeConstants), model.SQLServerType.ToString());
				root.SupportLegacySearchObject = false;
				root.UseUTCTime = model.UseUTCTime;
				root.Version = model.Version;
				root.PreCreateScript = model.PreCreateScript;

				#region Set Refactorizations
				foreach (nHydrate.Dsl.Objects.IRefactor r in model.Refactorizations)
				{
					if (r is nHydrate.Dsl.Objects.RefactorTableSplit)
					{
						var newR = new nHydrate.Generator.Common.GeneratorFramework.RefactorTableSplit();
						newR.EntityKey1 = (r as nHydrate.Dsl.Objects.RefactorTableSplit).EntityKey1;
						newR.EntityKey2 = (r as nHydrate.Dsl.Objects.RefactorTableSplit).EntityKey2;
						var flist = (r as nHydrate.Dsl.Objects.RefactorTableSplit).ReMappedFieldIDList;
						foreach (var k in flist.Keys)
							newR.ReMappedFieldIDList.Add(k, flist[k]);
						root.Refactorizations.Add(newR);
					}
					else if (r is nHydrate.Dsl.Objects.RefactorTableCombine)
					{
						var newR = new nHydrate.Generator.Common.GeneratorFramework.RefactorTableCombine();
						newR.EntityKey1 = (r as nHydrate.Dsl.Objects.RefactorTableCombine).EntityKey1;
						newR.EntityKey2 = (r as nHydrate.Dsl.Objects.RefactorTableCombine).EntityKey2;
						var flist = (r as nHydrate.Dsl.Objects.RefactorTableCombine).ReMappedFieldIDList;
						foreach (var k in flist.Keys)
							newR.ReMappedFieldIDList.Add(k, flist[k]);
						root.Refactorizations.Add(newR);
					}
				}
				#endregion

				if (ownerModule != null)
					root.ModuleName = ownerModule.Name;

				root.Database.CreatedByColumnName = model.CreatedByColumnName;
				root.Database.CreatedDateColumnName = model.CreatedDateColumnName;
				root.Database.ModifiedByColumnName = model.ModifiedByColumnName;
				root.Database.ModifiedDateColumnName = model.ModifiedDateColumnName;
				root.Database.TimestampColumnName = model.TimestampColumnName;
				root.Database.GrantExecUser = model.GrantUser;
				root.Database.Collate = model.Collate;

				var inheritanceConnectors = diagram.NestedChildShapes.Where(x => x is EntityInheritanceConnector).Cast<EntityInheritanceConnector>().ToList();

				#region Load the entities
				foreach (var entity in model.Entities.Where(x => x.IsGenerated).Where(x => ownerModule == null || x.Modules.Contains(ownerModule)))
				{
					#region Table Info
					var newTable = root.Database.Tables.Add();
					newTable.ResetKey(entity.Id.ToString());
					newTable.AllowAuditTracking = entity.AllowAuditTracking;
					newTable.AllowCreateAudit = entity.AllowCreateAudit;
					newTable.AllowModifiedAudit = entity.AllowModifyAudit;
					newTable.AllowTimestamp = entity.AllowTimestamp;
					newTable.AssociativeTable = entity.IsAssociative;
					newTable.CodeFacade = entity.CodeFacade;
					newTable.DBSchema = entity.Schema;
					newTable.Description = entity.Summary;
					newTable.EnforcePrimaryKey = entity.EnforcePrimaryKey;
					newTable.Generated = entity.IsGenerated;
					newTable.Immutable = entity.Immutable;
					newTable.TypedTable = (nHydrate.Generator.Models.TypedTableConstants)Enum.Parse(typeof(nHydrate.Generator.Models.TypedTableConstants), entity.TypedEntity.ToString(), true);
					newTable.Name = entity.Name;
					newTable.GeneratesDoubleDerived = entity.GeneratesDoubleDerived;
					newTable.SetKey(entity.Id.ToString());

					//Add metadata
					foreach (var md in entity.EntityMetadata)
					{
						newTable.MetaData.Add(new nHydrate.Generator.Models.MetadataItem() { Key = md.Key, Value = md.Value });
					}

					#endregion

					#region Load the fields for this entity
					var fieldList = entity.Fields.Where(x => x.IsGenerated).Where(x => ownerModule == null || x.Modules.Contains(ownerModule)).ToList();
					foreach (var field in fieldList.OrderBy(x => x.SortOrder))
					{
						var newColumn = root.Database.Columns.Add();
						newColumn.ResetKey(field.Id.ToString());
						newColumn.AllowNull = field.Nullable;
						newColumn.Collate = field.Collate;
						newColumn.CodeFacade = field.CodeFacade;
						newColumn.ComputedColumn = field.IsCalculated;
						newColumn.DataType = (System.Data.SqlDbType)Enum.Parse(typeof(System.Data.SqlDbType), field.DataType.ToString());
						newColumn.Default = field.Default;
						newColumn.Description = field.Summary;
						newColumn.Formula = field.Formula;
						newColumn.FriendlyName = field.FriendlyName;
						newColumn.Generated = field.IsGenerated;
						newColumn.Identity = (nHydrate.Generator.Models.IdentityTypeConstants)Enum.Parse(typeof(nHydrate.Generator.Models.IdentityTypeConstants), field.Identity.ToString());
						newColumn.IsIndexed = field.IsIndexed;
						newColumn.IsReadOnly = field.IsReadOnly;
						newColumn.IsUnique = field.IsUnique;
						newColumn.Length = field.Length;
						newColumn.Max = field.Max;
						newColumn.Min = field.Min;
						newColumn.Name = field.Name;
						newColumn.ParentTableRef = newTable.CreateRef();
						newColumn.PrimaryKey = field.IsPrimaryKey;
						newColumn.Scale = field.Scale;
						newColumn.ValidationExpression = field.ValidationExpression;
						newColumn.IsBrowsable = field.IsBrowsable;
						newColumn.Category = field.Category;
						newColumn.SortOrder = field.SortOrder;
						newTable.Columns.Add(newColumn.CreateRef());

						//Add metadata
						foreach (var md in field.FieldMetadata)
						{
							newColumn.MetaData.Add(new nHydrate.Generator.Models.MetadataItem() { Key = md.Key, Value = md.Value });
						}

					}
					#endregion

					#region Indexes
					foreach (var index in entity.Indexes)
					{
						var newIndex = new nHydrate.Generator.Models.TableIndex(newTable.Root)
						{
							Description = index.Summary,
							IsUnique = index.IsUnique,
							Clustered = index.Clustered,
							PrimaryKey = (index.IndexType == IndexTypeConstants.PrimaryKey)
						};
						newTable.TableIndexList.Add(newIndex);

						//Add index columns
						var indexColumns = index.IndexColumns.Where(x => x.GetField() != null).ToList();
						foreach (var ic in indexColumns.OrderBy(x => x.SortOrder).ThenBy(x => x.GetField().Name))
						{
							var field = ic.GetField();
							if (ownerModule == null || field.Modules.Contains(ownerModule))
							{
								var newColumn = new nHydrate.Generator.Models.TableIndexColumn(newTable.Root) { Ascending = ic.Ascending, FieldID = field.Id };
								newIndex.IndexColumnList.Add(newColumn);
							}
						}
					}

					#endregion

					#region Select Commands
					foreach (var command in entity.SelectCommands.Where(x => x.IsGenerated))
					{
						var newCommand = root.Database.CustomRetrieveRules.Add();
						newCommand.ResetKey(command.Id.ToString());
						newCommand.Description = command.Summary;
						newCommand.Generated = command.IsGenerated;
						newCommand.Name = command.Name;
						newCommand.SQL = command.SQL;
						newCommand.UseSearchObject = command.UseSearchObject;
						newTable.CustomRetrieveRules.Add(newCommand.CreateRef());
						newCommand.ParentTableRef = newTable.CreateRef();

						#region Create Parameters
						foreach (var parameter in command.Parameters)
						{
							var newParameter = root.Database.CustomRetrieveRuleParameters.Add();
							newParameter.ResetKey(parameter.Id.ToString());
							newParameter.AllowNull = parameter.Nullable;
							newParameter.DataType = (System.Data.SqlDbType)Enum.Parse(typeof(System.Data.SqlDbType), parameter.DataType.ToString());
							newParameter.Default = parameter.Default;
							newParameter.Description = parameter.Summary;
							newParameter.Generated = parameter.IsGenerated;
							newParameter.IsOutputParameter = parameter.IsOutputParameter;
							newParameter.Length = parameter.Length;
							newParameter.Scale = parameter.Scale;
							newParameter.Name = parameter.Name;
							newCommand.Parameters.Add(newParameter.CreateRef());
							newParameter.ParentTableRef = newCommand.CreateRef();
						}
						#endregion

					}
					#endregion

					#region Composites
					foreach (var composite in entity.Composites.Where(x => x.IsGenerated))
					{
						var newComponent = new nHydrate.Generator.Models.TableComponent(newTable.ComponentList.Root, newTable.ComponentList.Parent);
						newComponent.ResetKey(composite.Id.ToString());
						newComponent.CodeFacade = composite.CodeFacade;
						newComponent.Description = composite.Summary;
						newComponent.Generated = composite.IsGenerated;
						newComponent.Name = composite.Name;
						newTable.ComponentList.Add(newComponent);

						foreach (var column in composite.Fields)
						{
							var oldColumn = entity.Fields.FirstOrDefault(x => x.Id == column.Id);
							if (oldColumn != null)
							{
								var newColumn = newTable.GetColumns().FirstOrDefault(x => x.Name == oldColumn.Name);
								if (newColumn != null)
								{
									newComponent.Columns.Add(newColumn.CreateRef());
								}
							}
						}
					}
					#endregion

					#region Static Data
					if (entity.TypedEntity != TypedEntityConstants.None)
					{
						//Determine how many rows there are
						var orderKeyList = entity.StaticDatum.Select(x => x.OrderKey).Distinct().ToList();
						var rowCount = orderKeyList.Count();

						//Create a OLD static data row for each one
						for (int ii = 0; ii < rowCount; ii++)
						{
							//For each row create N cells one for each column
							var rowEntry = new nHydrate.Generator.Models.RowEntry(newTable.Root);
							for (var jj = 0; jj < fieldList.Count; jj++)
							{
								var cellEntry = new nHydrate.Generator.Models.CellEntry(newTable.Root);
								var column = newTable.GetColumns().ToList()[jj];
								cellEntry.ColumnRef = column.CreateRef();

								var currentColumn = fieldList.FirstOrDefault(x => x.Id == new Guid(column.Key));
								if (currentColumn != null)
								{
									var dataum = entity.StaticDatum.FirstOrDefault(x =>
										x.ColumnKey == currentColumn.Id &&
										x.OrderKey == orderKeyList[ii]);
									if (dataum != null)
										cellEntry.Value = dataum.Value;
								}

								//Add the cell to the row
								rowEntry.CellEntries.Add(cellEntry);

							}
							newTable.StaticData.Add(rowEntry);
						}
					}
					#endregion
				}

				//Setup inheritance
				foreach (var connector in inheritanceConnectors)
				{
					var b = connector.ToShape.ModelElement as Entity;
					var d = connector.FromShape.ModelElement as Entity;
					var baseTable = root.Database.Tables.FirstOrDefault(x => x.Name == b.Name);
					var derivedTable = root.Database.Tables.FirstOrDefault(x => x.Name == d.Name);
					if (d != null && b != null)
					{
						derivedTable.ParentTable = baseTable;
					}
				}

				#endregion

				#region Relations
				var relationConnectors = diagram.NestedChildShapes.Where(x => x is EntityAssociationConnector).Cast<EntityAssociationConnector>().ToList();
				foreach (var shape in relationConnectors)
				{
					if (shape is EntityAssociationConnector)
					{
						var connector = shape as EntityAssociationConnector;
						var parent = connector.FromShape.ModelElement as Entity;
						var child = connector.ToShape.ModelElement as Entity;

						var relation = connector.ModelElement as EntityHasEntities;
						var fieldList = model.RelationFields.Where(x => x.RelationID == relation.Id);

						var parentTable = root.Database.Tables.FirstOrDefault(x => x.Name == parent.Name);
						var childTable = root.Database.Tables.FirstOrDefault(x => x.Name == child.Name);

						//If we found both parent and child tables...
						if (parentTable != null && childTable != null)
						{
							bool isValidRelation = true;
							if (ownerModule != null)
							{
								isValidRelation = ((IModuleLink)relation).Modules.Contains(ownerModule);
							}

							if (model.UseModules && isValidRelation)
							{
								//If using modules then check that this relation's fields are in this module
								foreach (var columnSet in fieldList)
								{
									var field1 = parent.Fields.FirstOrDefault(x => x.Id == columnSet.SourceFieldId);
									var field2 = child.Fields.FirstOrDefault(x => x.Id == columnSet.TargetFieldId);
									if (!field1.Modules.Contains(ownerModule)) isValidRelation = false;
									if (!field2.Modules.Contains(ownerModule)) isValidRelation = false;
								}

							}

							if (isValidRelation)
							{
								var newRelation = root.Database.Relations.Add();
								newRelation.ParentTableRef = parentTable.CreateRef();
								newRelation.ChildTableRef = childTable.CreateRef();
								newRelation.RoleName = ((EntityHasEntities)connector.ModelElement).RoleName;
								if (ownerModule == null)
								{
									newRelation.Enforce = relation.IsEnforced;
								}
								else
								{
									var relationModule = model.RelationModules.FirstOrDefault(x => x.RelationID == relation.Id && x.ModuleId == ownerModule.Id);
									if (relationModule == null)
									{
										//I do not think this should ever happen??
										newRelation.Enforce = false;
									}
									else
									{
										newRelation.Enforce = relationModule.IsEnforced;
									}
								}

								//Create the column links
								foreach (var columnSet in fieldList)
								{
									var field1 = parent.Fields.FirstOrDefault(x => x.Id == columnSet.SourceFieldId);
									var field2 = child.Fields.FirstOrDefault(x => x.Id == columnSet.TargetFieldId);

									var column1 = parentTable.GetColumnsFullHierarchy().FirstOrDefault(x => x.Name == field1.Name);
									var column2 = childTable.GetColumnsFullHierarchy().FirstOrDefault(x => x.Name == field2.Name);

									newRelation.ColumnRelationships.Add(new nHydrate.Generator.Models.ColumnRelationship(root)
									{
										ParentColumnRef = column1.CreateRef(),
										ChildColumnRef = column2.CreateRef(),
									}
									);
								}

								//Actually add the relation to the collection
								if (newRelation.ColumnRelationships.Count > 0)
									parentTable.Relationships.Add(newRelation.CreateRef());
							}
							else
							{
								System.Diagnostics.Debug.Write(string.Empty);
							}

						}
						else
						{
							System.Diagnostics.Debug.Write(string.Empty);
						}

					}
				}

				//Setup inheritance relationships
				foreach (var connector in inheritanceConnectors)
				{
					var b = connector.ToShape.ModelElement as Entity;
					var d = connector.FromShape.ModelElement as Entity;
					var baseTable = root.Database.Tables.FirstOrDefault(x => x.Name == b.Name);
					var derivedTable = root.Database.Tables.FirstOrDefault(x => x.Name == d.Name);
					if (derivedTable != null && baseTable != null)
					{
						var newRelation = root.Database.Relations.Add();
						newRelation.ParentTableRef = baseTable.CreateRef();
						newRelation.ChildTableRef = derivedTable.CreateRef();
						newRelation.Enforce = true; //Hard-wired for now

						//Loop through the primary keys of each table and map
						foreach (var pk in baseTable.PrimaryKeyColumns)
						{
							var fk = derivedTable.GetColumns().FirstOrDefault(x => x.Name == pk.Name);
							if (fk != null)
							{
								newRelation.ColumnRelationships.Add(new nHydrate.Generator.Models.ColumnRelationship(root)
								{
									ParentColumnRef = pk.CreateRef(),
									ChildColumnRef = fk.CreateRef(),
								});
							}
						}
					}
				}

				#endregion

				#region Views
				foreach (var view in model.Views.Where(x => x.IsGenerated).Where(x => ownerModule == null || x.Modules.Contains(ownerModule)))
				{
					var newView = root.Database.CustomViews.Add();
					newView.ResetKey(view.Id.ToString());
					newView.CodeFacade = view.CodeFacade;
					newView.DBSchema = view.Schema;
					newView.Description = view.Summary;
					newView.Generated = view.IsGenerated;
					newView.Name = view.Name;
					newView.SQL = view.SQL;
					newView.GeneratesDoubleDerived = view.GeneratesDoubleDerived;
					newView.PrecedenceOrder = view.PrecedenceOrder;
					newView.SetKey(view.Id.ToString());

					foreach (var field in view.Fields)
					{
						var newField = root.Database.CustomViewColumns.Add();
						newField.ResetKey(field.Id.ToString());
						newField.AllowNull = field.Nullable;
						newField.CodeFacade = field.CodeFacade;
						newField.DataType = (System.Data.SqlDbType)Enum.Parse(typeof(System.Data.SqlDbType), field.DataType.ToString());
						newField.Default = field.Default;
						newField.Description = field.Summary;
						newField.FriendlyName = field.FriendlyName;
						newField.Generated = field.IsGenerated;
						newField.Length = field.Length;
						newField.Name = field.Name;
						newField.Scale = field.Scale;
						newView.Columns.Add(newField.CreateRef());
						newField.ParentViewRef = newView.CreateRef();
					}

				}
				#endregion

				#region Stored Procedures
				foreach (var storedProc in model.StoredProcedures.Where(x => x.IsGenerated).Where(x => ownerModule == null || x.Modules.Contains(ownerModule)))
				{
					var newStoredProc = root.Database.CustomStoredProcedures.Add();
					newStoredProc.ResetKey(storedProc.Id.ToString());
					newStoredProc.CodeFacade = storedProc.CodeFacade;
					newStoredProc.DatabaseObjectName = storedProc.DatabaseObjectName;
					newStoredProc.DBSchema = storedProc.Schema;
					newStoredProc.Description = storedProc.Summary;
					newStoredProc.Generated = storedProc.IsGenerated;
					newStoredProc.Name = storedProc.Name;
					newStoredProc.SQL = storedProc.SQL;
					newStoredProc.GeneratesDoubleDerived = storedProc.GeneratesDoubleDerived;
					newStoredProc.PrecedenceOrder = storedProc.PrecedenceOrder;
					newStoredProc.SetKey(storedProc.Id.ToString());

					foreach (var field in storedProc.Fields)
					{
						var newField = root.Database.CustomStoredProcedureColumns.Add();
						newField.ResetKey(field.Id.ToString());
						newField.AllowNull = field.Nullable;
						newField.CodeFacade = field.CodeFacade;
						newField.DataType = (System.Data.SqlDbType)Enum.Parse(typeof(System.Data.SqlDbType), field.DataType.ToString());
						newField.Default = field.Default;
						newField.Description = field.Summary;
						newField.FriendlyName = field.FriendlyName;
						newField.Generated = field.IsGenerated;
						newField.Length = field.Length;
						newField.Name = field.Name;
						newField.Scale = field.Scale;
						newStoredProc.Columns.Add(newField.CreateRef());
						newField.ParentRef = newStoredProc.CreateRef();
					}

					//Just in case these are ordered get all sort-ordered parameters first then take on all unordred alphabetized parmameters
					var orderedParameters = storedProc.Parameters.Where(x => x.SortOrder > 0).OrderBy(x => x.SortOrder).ToList();
					orderedParameters.AddRange(storedProc.Parameters.Where(x => x.SortOrder == 0).OrderBy(x => x.Name).ToList());
					foreach (var parameter in orderedParameters)
					{
						var newParameter = root.Database.CustomRetrieveRuleParameters.Add();
						newParameter.ResetKey(parameter.Id.ToString());
						newParameter.AllowNull = parameter.Nullable;
						newParameter.CodeFacade = parameter.CodeFacade;
						newParameter.DataType = (System.Data.SqlDbType)Enum.Parse(typeof(System.Data.SqlDbType), parameter.DataType.ToString());
						newParameter.Default = parameter.Default;
						newParameter.Description = parameter.Summary;
						newParameter.Generated = parameter.IsGenerated;
						newParameter.IsOutputParameter = parameter.IsOutputParameter;
						newParameter.Length = parameter.Length;
						newParameter.Name = parameter.Name;
						newParameter.Scale = parameter.Scale;
						newParameter.SortOrder = parameter.SortOrder;
						newStoredProc.Parameters.Add(newParameter.CreateRef());
						newParameter.ParentTableRef = newStoredProc.CreateRef();
					}

				}
				#endregion

				#region Functions
				foreach (var function in model.Functions.Where(x => x.IsGenerated).Where(x => ownerModule == null || x.Modules.Contains(ownerModule)))
				{
					var newFunction = root.Database.Functions.Add();
					newFunction.ResetKey(function.Id.ToString());
					newFunction.CodeFacade = function.CodeFacade;
					newFunction.DBSchema = function.Schema;
					newFunction.Description = function.Summary;
					newFunction.Generated = function.IsGenerated;
					newFunction.Name = function.Name;
					newFunction.SQL = function.SQL;
					newFunction.IsTable = function.IsTable;
					newFunction.ReturnVariable = function.ReturnVariable;
					newFunction.PrecedenceOrder = function.PrecedenceOrder;
					newFunction.SetKey(function.Id.ToString());

					foreach (var field in function.Fields)
					{
						var newField = root.Database.FunctionColumns.Add();
						newField.ResetKey(field.Id.ToString());
						newField.AllowNull = field.Nullable;
						newField.CodeFacade = field.CodeFacade;
						newField.DataType = (System.Data.SqlDbType)Enum.Parse(typeof(System.Data.SqlDbType), field.DataType.ToString());
						newField.Default = field.Default;
						newField.Description = field.Summary;
						newField.FriendlyName = field.FriendlyName;
						newField.Generated = field.IsGenerated;
						newField.Length = field.Length;
						newField.Name = field.Name;
						newField.Scale = field.Scale;
						newFunction.Columns.Add(newField.CreateRef());
						newField.ParentRef = newFunction.CreateRef();
					}

					//Just in case these are ordered get all sort-ordered parameters first then take on all unordred alphabetized parmameters
					var orderedParameters = function.Parameters.Where(x => x.SortOrder > 0).OrderBy(x => x.SortOrder).ToList();
					orderedParameters.AddRange(function.Parameters.Where(x => x.SortOrder == 0).OrderBy(x => x.Name).ToList());
					foreach (var parameter in orderedParameters)
					{
						var newParameter = root.Database.FunctionParameters.Add();
						newParameter.ResetKey(parameter.Id.ToString());
						newParameter.AllowNull = parameter.Nullable;
						newParameter.CodeFacade = parameter.CodeFacade;
						newParameter.DataType = (System.Data.SqlDbType)Enum.Parse(typeof(System.Data.SqlDbType), parameter.DataType.ToString());
						newParameter.Default = parameter.Default;
						newParameter.Description = parameter.Summary;
						newParameter.Generated = parameter.IsGenerated;
						newParameter.Length = parameter.Length;
						newParameter.Name = parameter.Name;
						newParameter.Scale = parameter.Scale;
						newParameter.SortOrder = parameter.SortOrder;

						var r = newParameter.CreateRef();
						r.RefType = nHydrate.Generator.Models.ReferenceType.FunctionParameter;
						newFunction.Parameters.Add(r);
						newParameter.ParentTableRef = newFunction.CreateRef();
					}

				}
				#endregion

				return root;

			}
			catch (Exception ex)
			{
				throw;
			}
		}

		public static bool Validate(nHydrateDocData docData, Microsoft.VisualStudio.Modeling.Store store, nHydrateModel model)
		{
			var key = ProgressHelper.ProgressingStarted("Verifying Model...");
			try
			{
				var validationController = docData.ValidationController;
				validationController.Validate(model.Entities, Microsoft.VisualStudio.Modeling.Validation.ValidationCategories.Custom);
				return validationController.Validate(store, Microsoft.VisualStudio.Modeling.Validation.ValidationCategories.Custom |
																						Microsoft.VisualStudio.Modeling.Validation.ValidationCategories.Load |
																						Microsoft.VisualStudio.Modeling.Validation.ValidationCategories.Menu |
																						Microsoft.VisualStudio.Modeling.Validation.ValidationCategories.Open |
																						Microsoft.VisualStudio.Modeling.Validation.ValidationCategories.Save);
			}
			catch (Exception ex)
			{
				ProgressHelper.ProgressingComplete(key);
				throw;
			}
			finally
			{
				ProgressHelper.ProgressingComplete(key);
			}
		}

		private class GenerateThreader
		{
			private nHydrateModel _model = null;
			private Microsoft.VisualStudio.Modeling.Store _store = null;
			private Microsoft.VisualStudio.Modeling.Shell.ModelingDocData _docData = null;
			private DateTime _startTime = DateTime.Now;
			private int _totalFileCount = 0;
			private int _processedFileCount = 0;
			private IEnumerable< nHydrate.Generator.Common.GeneratorFramework.IGenerator> _generators = null;
			private int FilesSkipped { get; set; }
			private int FilesSuccess { get; set; }
			private int FilesFailed { get; set; }
			public List<nHydrate.Generator.Common.EventArgs.ProjectItemGeneratedEventArgs> GeneratedFileList { get; private set; }
			public List<string> ErrorList { get; private set; }

			public GenerateThreader(IEnumerable<nHydrate.Generator.Common.GeneratorFramework.IGenerator> generators, nHydrateModel model, Microsoft.VisualStudio.Modeling.Store store, Microsoft.VisualStudio.Modeling.Shell.ModelingDocData docData)
			{
				this.ErrorList = new List<string>();
				GeneratedFileList = new List<nHydrate.Generator.Common.EventArgs.ProjectItemGeneratedEventArgs>();
				_generators = generators;
				_model = model;
				_store = store;
				_docData = docData;
			}

			internal void Run()
			{
				_totalFileCount = 0;
				_processedFileCount = 0;
				var pkey = string.Empty;
				try
				{
					var startTime = DateTime.Now;
					try
					{
						var g = new nHydrate.Generator.Common.GeneratorFramework.GeneratorHelper();
						g.ProjectItemGenerated += new nHydrate.Generator.Common.GeneratorFramework.ProjectItemGeneratedEventHandler(g_ProjectItemGenerated);

						var excludeList = new List<Type>();
						var generatorTypeList = g.GetProjectGenerators(_generators.First());
						if (generatorTypeList.Count == 0)
							return;

						//Show wizard first
						var F1 = new GenerateSettingsWizard(_generators.First(), generatorTypeList);
						List<System.Type> selectedTypes = null;
						if (F1.IsValid)
						{
							if (F1.ShowDialog() == DialogResult.OK)
							{
								selectedTypes = F1.SelectGenerators;
							}
						}

						//Show generator list
						using (var F = new GenerateSettings(_generators.First(), generatorTypeList, selectedTypes))
						{
							if (F.ShowDialog() != DialogResult.OK) return;
							excludeList = F.ExcludeList;
						}

						if (_generators.Count() > 0)
						{
							pkey = ProgressHelper.ProgressingStarted("Generating...", false, 240000); //Put a 4 minute timer on it
							foreach (var item in _generators)
							{
								_totalFileCount += g.GetFileCount(item, excludeList);
							}
							System.Diagnostics.Debug.WriteLine("File count: " + _totalFileCount);

							//Save document
							(_docData as nHydrateDocData).Save(_docData.FileName, 1, 0);

							_startTime = DateTime.Now;
							foreach (var item in _generators)
							{
								g.GenerateAll(item, excludeList);
							}

							if (_model.Refactorizations.Count > 0)
							{
								using (var transaction = _store.TransactionManager.BeginTransaction(Guid.NewGuid().ToString()))
								{
									_model.Refactorizations.Clear();
									_model.UseUTCTime = !_model.UseUTCTime; //Trigger a model change
									_model.UseUTCTime = !_model.UseUTCTime; //Keep this line too
									transaction.Commit();

									//Save document
									(_docData as nHydrateDocData).Save(_docData.FileName, 1, 0);

								}
							}
						}

						this.ErrorList = g.GetErrorList().ToList();

					}
					catch (Exception ex)
					{
						throw;
					}
					finally
					{
						ProgressHelper.ProgressingComplete(pkey);
					}

					var endTime = DateTime.Now;
					var duration = endTime.Subtract(startTime);
					using (var F = new StatisticsForm())
					{
						var text = "The generation was successful.\r\n\r\n";
						text += "Files generated: " + this.FilesSuccess + "\r\n";
						text += "Files skipped: " + this.FilesSkipped + "\r\n";
						text += "Files failed: " + this.FilesFailed + "\r\n";
						text += "\r\n\r\n";
						text += "Generation time: " + duration.Hours.ToString("00") + ":" +
								duration.Minutes.ToString("00") + ":" +
								duration.Seconds.ToString("00");
						F.DisplayText = text;
						F.GeneratedFileList = this.GeneratedFileList;
						F.ShowDialog();
					}

				}
				catch (Exception ex)
				{
					ProgressHelper.ProgressingComplete(pkey);
					GlobalHelper.ShowError(ex);
				}
			}

			private void g_ProjectItemGenerated(object sender, nHydrate.Generator.Common.EventArgs.ProjectItemGeneratedEventArgs e)
			{
				try
				{
					if (e.FileState == EnvDTEHelper.FileStateConstants.Skipped)
						this.FilesSkipped++;
					if (e.FileState == EnvDTEHelper.FileStateConstants.Success)
						this.FilesSuccess++;
					if (e.FileState == EnvDTEHelper.FileStateConstants.Failed)
						this.FilesFailed++;

					this.GeneratedFileList.Add(e);

					_processedFileCount++;
					int progress = -1;
					var mainText = string.Empty;
					var timeDisplay = string.Empty;
					if (_totalFileCount > 0)
					{
						progress = (_processedFileCount * 100) / _totalFileCount;
						if (progress > 100) progress = 100;
						mainText = "Generating " + _processedFileCount.ToString("###,###,###,##0") + " of " + _totalFileCount.ToString("###,###,###,##0") + "...";
						if (progress > 0)
						{
							var elapsedTime = DateTime.Now.Subtract(_startTime).TotalSeconds;
							var totalTime = elapsedTime * (1 / (progress / 100.0));
							timeDisplay = "(" + Extensions.ToElapsedTimeString(elapsedTime) + " of " + Extensions.ToElapsedTimeString(totalTime) + ")";
						}
					}

					ProgressHelper.UpdateSubText(mainText, e.FullName, progress, timeDisplay);
				}
				catch (Exception ex)
				{
					//TODO
				}
			}

		}

	}
}
